/*

========================================================================

	                        Light Prepass Renderer

		                        by Floris Groen

                         E-mail : fmfgroen@yahoo.com
                     Blog : www.florisgroen.blogspot.com
		      

========================================================================

*/

using System;
using LightPrePassRenderer.Engine;
using LightPrePassRenderer.Engine.Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace LightPrePassRenderer
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class LightPrePassRendererSample : Game
    {
        GraphicsDeviceManager graphics;

        ILightManager lightManager;
        IRenderManager renderManager;
        Renderer renderer;

        Camera camera;

        ModelInstance ship1;
        Material ship1Material;
        Matrix ship1World;

        ModelInstance ship2;
        Material ship2Material;
        Matrix ship2World;

        ModelInstance floor;
        Material floorMaterial;
        Matrix floorWorld;

        float rotation;

        Vector2 previousMousePosition;

        public LightPrePassRendererSample()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            graphics.PreparingDeviceSettings += graphics_PreparingDeviceSettings;
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            graphics.IsFullScreen = true;
            graphics.ApplyChanges();

            lightManager = new LightManager();
            Services.AddService(typeof(ILightManager), lightManager);
    
            renderManager = new RenderManager();
            Services.AddService(typeof(IRenderManager), renderManager);

            renderer = new Renderer(GraphicsDevice, Services, Content);
            Services.AddService(typeof(IRenderer), renderer);

            camera = new Camera
            {
                Position = new Vector3(0, 20, -70),
                NearPlane = 10.0f,
                FarPlane = 400.0f,
                AspectRatio = 1280.0f / 720.0f
            };
            camera.Update();

            renderManager.Camera = camera;

            lightManager.CreateDirectionalLight(Color.WhiteSmoke, 1.0f, new Vector3(-1, -1, 0));

            //Create a grid of lights with a bunch of funny colors.
            lightManager.CreatePointLight(Color.Goldenrod, 3.0f, new Vector3(-75, 10, -75), 30.0f);
            lightManager.CreatePointLight(Color.Honeydew, 3.0f, new Vector3(-25, 5, -75), 30.0f);
            lightManager.CreatePointLight(Color.Orchid, 3.0f, new Vector3(25, 10, -75), 30.0f);
            lightManager.CreatePointLight(Color.SeaShell, 3.0f, new Vector3(75, 10, -75), 30.0f);

            lightManager.CreatePointLight(Color.Tomato, 3.0f, new Vector3(-75, 10, -25), 30.0f);
            lightManager.CreatePointLight(Color.Crimson, 3f, new Vector3(-25, 5, -25), 30.0f);
            lightManager.CreatePointLight(Color.Thistle, 3.0f, new Vector3(25, 10, -25), 30.0f);
            lightManager.CreatePointLight(Color.RoyalBlue, 3.0f, new Vector3(75, 10, -25), 30.0f);

            lightManager.CreatePointLight(Color.Chartreuse, 3f, new Vector3(-75, 5, 25), 30.0f);
            lightManager.CreatePointLight(Color.PapayaWhip, 3f, new Vector3(-25, 10, 25), 30.0f);
            lightManager.CreatePointLight(Color.Plum, 3.0f, new Vector3(25, 10, 25), 30.0f);
            lightManager.CreatePointLight(Color.OliveDrab, 3.0f, new Vector3(75, 5, 25), 30.0f);

            lightManager.CreatePointLight(Color.Firebrick, 3.0f, new Vector3(-75, 10, 75), 30.0f);
            lightManager.CreatePointLight(Color.PeachPuff, 3f, new Vector3(-25, 10, 75), 30.0f);
            lightManager.CreatePointLight(Color.Sienna, 3.0f, new Vector3(25, 10, 75), 30.0f);
            lightManager.CreatePointLight(Color.Peru, 3.0f, new Vector3(75, 5, 75), 30.0f);

            base.Initialize();
        }

        void graphics_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            e.GraphicsDeviceInformation.PresentationParameters.AutoDepthStencilFormat = DepthFormat.Depth24Stencil8;
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {            
            floor = new ModelInstance(Content.Load<Model>("Models\\table"));
            
            floorMaterial = new Material();
            floorMaterial.Texture = Content.Load<Texture2D>("Models\\wood");
            floorMaterial.Normals = Content.Load<Texture2D>("Models\\wood_NORM");
            floorMaterial.SpecularPower = 0.05f;
            floorMaterial.SpecularIntensity = 0.2f;
            floorMaterial.SpecularColor = Color.BurlyWood;
            floorMaterial.DiffuseColor = Color.White;
            
            floorWorld = Matrix.CreateTranslation(new Vector3(0, -5, 0));

            ship1 = new ModelInstance(Content.Load<Model>("Models\\ship1"));
            
            ship1Material = new Material();
            ship1Material.Texture = Content.Load<Texture2D>("Models\\ship1_c");
            ship1Material.Normals = Content.Load<Texture2D>("Models\\ship1_n");
            ship1Material.SpecularPower = 0.3f;
            ship1Material.SpecularIntensity = 0.8f;
            ship1Material.SpecularColor = Color.White;
            ship1Material.DiffuseColor = Color.AliceBlue;
            
            ship1World = Matrix.CreateTranslation(new Vector3(-20, 5, 0));

            ship2 = new ModelInstance(Content.Load<Model>("Models\\ship2"));
            
            ship2Material = new Material();
            ship2Material.Texture = Content.Load<Texture2D>("Models\\ship2_c");
            ship2Material.Normals = Content.Load<Texture2D>("Models\\ship2_n");
            ship2Material.SpecularPower = 0.3f;
            ship2Material.SpecularIntensity = 0.8f;
            ship2Material.SpecularColor = Color.SlateGray;
            ship2Material.DiffuseColor = Color.WhiteSmoke;
            
            ship2World = Matrix.CreateTranslation(new Vector3(30, 5, 0));

            renderer.Initialize();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            renderer.Deinitialize();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            {
                this.Exit();
            }

            KeyboardState keyboardState = Keyboard.GetState();

            if (keyboardState.IsKeyDown(Keys.Escape))
            {
                Exit();
            }

            if (keyboardState.IsKeyDown(Keys.Up))
            {
                camera.Move(1);
            }

            if (keyboardState.IsKeyDown(Keys.Down))
            {
                camera.Move(-1);
            }

            if (keyboardState.IsKeyDown(Keys.Right))
            {
                camera.Strafe(1);
            }

            if (keyboardState.IsKeyDown(Keys.Left))
            {
                camera.Strafe(-1);
            }

            MouseState mouseState = Mouse.GetState();
            Vector2 mousePosition = new Vector2(mouseState.X, mouseState.Y);

            if (mouseState.LeftButton == ButtonState.Pressed)
            {
                camera.Pan((previousMousePosition.X - mousePosition.X)*0.005f);
                camera.Tilt((previousMousePosition.Y - mousePosition.Y)*0.005f);
            }

            previousMousePosition = mousePosition;

            renderManager.AddToRenderQueue(ship1, ref ship1World, ship1Material);
            renderManager.AddToRenderQueue(ship2, ref ship2World, ship2Material);

            renderManager.AddToRenderQueue(floor, ref floorWorld, floorMaterial);

            //We'll rotate the grid of lights so we see something going on, instead of a static scene.
            rotation = (float)Math.PI * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000;

            Matrix rotationMatrix = Matrix.CreateRotationY(rotation);

            for (int i = 0; i < lightManager.PointLightCount; i++)
            {
                PointLight light = lightManager.GetPointLight(i);
                light.Position = Vector3.Transform(light.Position, rotationMatrix);
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            renderer.Draw(gameTime);

            base.Draw(gameTime);
        }
    }
}
